package tech.xs.auth.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import tech.xs.common.auth.domain.cache.AuthPathCache;
import tech.xs.common.auth.domain.constant.AuthApiConstant;
import tech.xs.auth.dao.AuthPathPermissionDao;
import tech.xs.auth.dao.AuthPathRoleDao;
import tech.xs.auth.dao.AuthPathWhiteDao;
import tech.xs.auth.dao.AuthSysUserDao;
import tech.xs.auth.domain.entity.AuthPathPermission;
import tech.xs.auth.domain.entity.AuthPathRole;
import tech.xs.auth.domain.entity.AuthPathWhite;
import tech.xs.auth.domain.entity.AuthSysUser;
import tech.xs.auth.domain.exception.AuthException;
import tech.xs.auth.service.AuthPasswordService;
import tech.xs.auth.service.AuthPathService;
import tech.xs.auth.service.AuthSysUserService;
import tech.xs.auth.service.AuthTokenService;
import tech.xs.common.auth.domain.cache.AuthTokenCache;
import tech.xs.common.auth.domain.enums.AuthPathWhiteTypeEnum;
import tech.xs.common.auth.util.AuthTokenUtil;
import tech.xs.common.domain.enums.YesNoEnum;
import tech.xs.common.framework.domain.constant.Symbol;
import tech.xs.common.framework.domain.exception.BaseException;
import tech.xs.common.framework.domain.enums.CommonResultEnum;
import tech.xs.common.framework.domain.model.ApiResult;
import tech.xs.common.framework.util.Assert;
import tech.xs.common.framework.util.AuthHeaderUtil;
import tech.xs.common.framework.util.PermissionUtil;
import tech.xs.common.framework.util.RoleUtil;
import tech.xs.sys.api.SysUserRoleApi;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class PathServiceImpl implements AuthPathService {

    private static final String LOCK_RELOAD_AUTH_PATH_CACHE_KEY = "auth:lock:path:cache";

    @Resource
    private RedissonClient redissonClient;
    @Resource
    private AuthTokenService authTokenService;
    @Resource
    private AuthPathWhiteDao authPathWhiteDao;
    @Resource
    private AuthPathRoleDao authPathRoleDao;
    @Resource
    private AuthPathPermissionDao authPathPermissionDao;
    @Resource
    private AuthSysUserDao loginUserDao;
    @Resource
    private AuthSysUserService authSysUserService;
    @Resource
    private AuthPasswordService authPasswordService;
    @Resource
    private SysUserRoleApi sysUserRoleApi;

    @Override
    public ApiResult<Boolean> isPathAuthBasic(String path, String method) {
        AuthHeaderUtil.AuthHeader headToken = AuthHeaderUtil.getAuthHeader();

        String userName = headToken.getUserName();
        String password = headToken.getPassword();
        if (StrUtil.isBlank(userName) || StrUtil.isBlank(password)) {
            throw new AuthException("用户名和密码都不能为空");
        }
        AuthSysUser user = loginUserDao.selectOne(Wrappers.<AuthSysUser>lambdaQuery().eq(AuthSysUser::getUserName, userName));
        authSysUserService.checkLoginUser(user);
        password = authPasswordService.privateEncryptedLoginPassword(authPasswordService.publicEncryptedLoginPassword(password));
        Assert.isTrue(user.getPassword().equals(password), "用户不存在或密码错误", false);
        ApiResult<Set<String>> userRolesRes = sysUserRoleApi.getRoleCodesByUserName(userName);

        String cacheKey = AuthApiConstant.AUTH_PATH_CACHE_KEY + method.toUpperCase() + Symbol.COLON + path;
        RBucket<AuthPathCache> bucket = redissonClient.getBucket(cacheKey);
        AuthPathCache authPath = bucket.get();
        return pathAuthCheck(authPath, userRolesRes.getData());
    }

    private ApiResult<Boolean> pathAuthCheck(AuthPathCache authPath, Set<String> userRoles) {
        if (authPath.isLoginWhite()) {
            return ApiResult.success(true);
        }
        Set<String> pathPermissions = authPath.getPermissions();
        Set<String> pathRoles = authPath.getRoles();
        userRoles = RoleUtil.getEnableRole(userRoles);
        if (userRoles != null && pathRoles != null) {
            for (String userRole : userRoles) {
                if (pathRoles.contains(userRole)) {
                    return ApiResult.success(true);
                }
            }
        }
        Set<String> userPermissions = PermissionUtil.getEnableRolePermission(userRoles);
        if (userPermissions != null && pathPermissions != null) {
            for (String userPermission : userPermissions) {
                if (pathPermissions.contains(userPermission)) {
                    return ApiResult.success(true);
                }
            }
        }
        return ApiResult.error(CommonResultEnum.permissionDenied);
    }

    @Override
    public void reloadAllAuthPathCache() {
        RLock lock = getLoadAuthPathCachetWriteLock();
        log.info("开始加载uri权限缓存");
        try {
            Map<String, AuthPathCache> cacheMap = new HashMap<>();

            setAuthPathWhiteCache(cacheMap);
            setAuthPathRoleCache(cacheMap);
            setAuthPathPermissionCache(cacheMap);
            resetAuthPathCache(cacheMap);

        } catch (Exception e) {
            log.error("加载权限缓存异常", e);
            throw e;
        } finally {
            lock.unlock();
        }
        log.info("uri权限缓存加载完毕");
    }

    private void resetAuthPathCache(Map<String, AuthPathCache> cacheMap) {
        Set<String> newKeys = cacheMap.keySet();
        Iterable<String> keysByPattern = redissonClient.getKeys().getKeysByPattern(AuthApiConstant.AUTH_PATH_CACHE_KEY + Symbol.ASTERISK);
        Set<String> oldKeys = new HashSet<>();
        for (String item : keysByPattern) {
            oldKeys.add(item);
        }
        for (String key : newKeys) {
            AuthPathCache cache = cacheMap.get(key);
            String cacheKey = AuthApiConstant.AUTH_PATH_CACHE_KEY + key;
            RBucket<AuthPathCache> bucket = redissonClient.getBucket(cacheKey);
            bucket.set(cache);
            oldKeys.remove(cacheKey);
        }
        for (String oldKey : oldKeys) {
            redissonClient.getKeys().delete(oldKey);
        }
    }

    private void setAuthPathPermissionCache(Map<String, AuthPathCache> cacheMap) {
        List<AuthPathPermission> pathPermissions = authPathPermissionDao.selectList(Wrappers.<AuthPathPermission>lambdaQuery().eq(AuthPathPermission::getStatus, YesNoEnum.YES));
        for (AuthPathPermission item : pathPermissions) {
            String key = item.getMethod().name() + Symbol.COLON + item.getPath();
            AuthPathCache cache = cacheMap.get(key);
            if (cache == null) {
                cache = new AuthPathCache();
                cacheMap.put(key, cache);
            }
            cache.getPermissions().add(item.getPermissionCode());
        }
    }

    private void setAuthPathRoleCache(Map<String, AuthPathCache> cacheMap) {
        List<AuthPathRole> pathRoles = authPathRoleDao.selectList(Wrappers.<AuthPathRole>lambdaQuery().eq(AuthPathRole::getStatus, YesNoEnum.YES));
        for (AuthPathRole item : pathRoles) {
            String key = item.getMethod().name() + Symbol.COLON + item.getPath();
            AuthPathCache cache = cacheMap.get(key);
            if (cache == null) {
                cache = new AuthPathCache();
                cacheMap.put(key, cache);
            }
            cache.getRoles().add(item.getRoleCode());
        }
    }

    private void setAuthPathWhiteCache(Map<String, AuthPathCache> cacheMap) {
        List<AuthPathWhite> pathWhites = authPathWhiteDao.selectList(Wrappers.<AuthPathWhite>lambdaQuery().eq(AuthPathWhite::getStatus, YesNoEnum.YES));
        for (AuthPathWhite item : pathWhites) {
            if (AuthPathWhiteTypeEnum.EMPTY.equals(item.getType())) {
                continue;
            }
            String key = item.getMethod().name() + Symbol.COLON + item.getPath();
            AuthPathCache cache = cacheMap.get(key);
            if (cache == null) {
                cache = new AuthPathCache();
                cacheMap.put(key, cache);
            }
            if (AuthPathWhiteTypeEnum.LOGIN.equals(item.getType())) {
                cache.setLoginWhite(true);
            } else if (AuthPathWhiteTypeEnum.ALL.equals(item.getType())) {
                cache.setAllWhite(true);
            }
        }
    }

    /**
     * 获取权限缓存加载锁
     *
     * @return 缓存读写锁中的写锁
     */
    @SneakyThrows
    public RLock getLoadAuthPathCachetWriteLock() {
        // 加锁,过期时间一分钟
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(LOCK_RELOAD_AUTH_PATH_CACHE_KEY);
        RLock lock = readWriteLock.writeLock();
        if (lock.isLocked()) {
            if (!lock.tryLock(1, TimeUnit.MINUTES)) {
                throw new BaseException("获取权限缓存加载锁失败");
            }
        }
        lock.lock(1, TimeUnit.MINUTES);
        return lock;
    }

}
